<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>测试题目</title>
	<style>
		.canvas-wrapper{
			position: relative;
			margin: 100px auto;
		}
		.rate {
			position: absolute; 
			margin-top:-16px; 
			text-align: left;
			font-size: 12px; 
		}
		.rate.right{
			right: 0;
			text-align: right;
		}
	</style>
</head>
<body>
	
	<script>
        function addPie({r = 100,width = 450,height = 400,data = []} = {}) {

            let div = document.createElement('div'); 
            let fgt = document.createDocumentFragment();
            let cns = document.createElement('canvas');
            let ctx = cns.getContext('2d');
            let w = width;
            let h = height;
            let originX = w / 2;
            let originY = h / 2;
            let points = [];
            let total = data.reduce(function(v, item) {
                return v + item.cost;
            }, 0)
            let sAngel = -.5 * Math.PI;
            let eAngel = -.5 * Math.PI;
            let aAngel = Math.PI * 2;
            let pointR = r + 10;
            cns.width = ctx.width = w;
            cns.height = ctx.height = h;
            div.style.cssText = `width:${w}px;height:${h}px;`;
            div.className = 'canvas-wrapper';
            div.appendChild(cns);


            let cAngel;

            for (let i = 0, len = data.length; i < len; i++) { /* 绘制不同消费的份额 */

                eAngel = sAngel + aAngel / total * data[i].cost;

                _drawArc(ctx, {
                    origin: [originX, originY],
                    color: data[i].color,
                    r,
                    sAngel,
                    eAngel
                })

                /* 保存每个消费线条的起始点 */
                cAngel = 0.5 * Math.PI + sAngel + (eAngel - sAngel) / 2;
                points.push([originX + Math.sin(cAngel) * pointR, originY - Math.cos(cAngel) * pointR])

                sAngel = eAngel;

            }

            let leftPoints = [],
                rightPoints = [];

            for (let i = 0, len = points.length; i < len; i++) { /* 绘制起始点的小圆点，并分出左右 */

                _drawArc(ctx, {
                    origin: points[i],
                    color: data[i].color,
                    r: 2
                })

                if (points[i][0] < originX) { /* x坐标小于原点x坐标，在左边 */
                    leftPoints.push({
                        point: points[i],
                        top: points[i][1] < originY,
                        endPoint: [0, points[i][1]]
                    });
                } else { /* 否则在右边*/
                    rightPoints.push({
                        point: points[i],
                        top: points[i][1] < originY,
                        endPoint: [w, points[i][1]]
                    });
                }
            }


            let i = 0;
            let minPadding = 36;
            window.rightPoints = rightPoints;


            function _isUseable(arr) { /* 判断是否挤在一起 */
                if (arr.length <= 1)
                    return true;
                return arr.every(function(p, index, arr) {
                    if (index === 0) {
                        return true;
                    } else {
                        return p.endPoint[1] - arr[index - 1].endPoint[1] >= minPadding;
                    }
                })
            }

            function _makeUseable(arr, left) {/* 处理挤在一起的情况 */
                let diff, turingAngel, x, maths = Math.sin,
                    diffH, l;
                while (!_isUseable(arr)) {
                    for (let i = 0, len = arr.length - 1; i < len; i++) {
                        diff = arr[i + 1].endPoint[1] - arr[i].endPoint[1];
                        if (diff < minPadding) {
                            if (arr[i].top && arr[i + 1].top) {
                                if (arr[i].endPoint[1] - (minPadding - diff) > 0 && arr[i].endPoint[1] > i * minPadding) {
                                    arr[i].endPoint[1] = arr[i].endPoint[1] - (minPadding - diff);
                                } else {
                                    arr[i].endPoint[1] = i * minPadding;
                                    arr[i + 1].endPoint[1] = arr[i + 1].endPoint[1] + (minPadding - diff);
                                }
                            } else {
                                if (arr[i + 1].endPoint[1] + (minPadding - diff) < h && arr[i + 1].endPoint[1] < h - (len - i - 1) * minPadding) {
                                    arr[i + 1].endPoint[1] = arr[i + 1].endPoint[1] + (minPadding - diff)
                                } else {
                                    arr[i + 1].endPoint[1] = h - (len - i - 1) * minPadding;
                                    arr[i].endPoint[1] = arr[i].endPoint[1] - (minPadding - diff);
                                }
                            }
                            break;
                        }
                    }
                }

                for (let i = 0, len = arr.length; i < len; i++) { /* 起点和终点不在同一水平线上，设置转折点 */
                    if (arr[i].point[1] !== arr[i].endPoint[1]) {
                        turingAngel = 1 / 3 * Math.PI;
                        diffH = arr[i].endPoint[1] - arr[i].point[1];
                        l = Math.abs(arr[i].endPoint[0] - arr[i].point[0]);
                        x = Math.abs(maths(turingAngel) * diffH);
                        while (x > (l - 40)) {
                            turingAngel /= 2;
                            x = maths(turingAngel) * (arr[i].endPoint[1] - arr[i].point[1]);
                        }
                        arr[i].turingPoint = [arr[i].point[0] + (left ? -x : x), arr[i].endPoint[1]]
                    }
                }
            }


            _makeUseable(rightPoints); /*处理右边挤在一起的情况*/
            _makeUseable(leftPoints.reverse(), true);
            leftPoints.reverse(); /*处理左边挤在一起的情况*/

            let text;

            for (let j = 0, len = rightPoints.length; j < len; j++) { /* 绘制右侧线条，并添加文本DOM*/
                ctx.beginPath();
                ctx.moveTo(...rightPoints[j].point);
                ctx.strokeStyle = data[i].color;
                if (rightPoints[j].turingPoint)
                    ctx.lineTo(...rightPoints[j].turingPoint);
                ctx.lineTo(...rightPoints[j].endPoint);
                ctx.stroke();
                text = document.createElement("div");
                text = _addRate({direct:'right',data:data[i], point:rightPoints[j]});
                fgt.appendChild(text);
                i++;
            }

            for (let j = 0, len = leftPoints.length; j < len; j++) { /* 绘制左侧线条，并添加文本DOM*/
                ctx.beginPath();
                ctx.moveTo(...leftPoints[j].point);
                ctx.strokeStyle = data[i].color;
                if (leftPoints[j].turingPoint)
                    ctx.lineTo(...leftPoints[j].turingPoint);
                ctx.lineTo(...leftPoints[j].endPoint);
                ctx.stroke();
                text = _addRate({data:data[i], point:leftPoints[j]});
                fgt.appendChild(text);
                i++;
            }

            div.appendChild(fgt);
            document.body.appendChild(div); /* 添加到body */

            /* 再绘制一个圆，实现圆环效果 */
            _drawArc(ctx, {
                origin: [originX, originY],
                r: r / 5 * 3
            })

            /* 画圆 */
            function _drawArc(ctx, {color = '#fff',origin = [0, 0],r = 100,sAngel = 0, eAngel = 2 * Math.PI}) {
                ctx.beginPath();
                ctx.strokeStyle = color
                ctx.fillStyle = color;
                ctx.moveTo(...origin)
                ctx.arc(origin[0], origin[1], r, sAngel, eAngel);
                ctx.fill();
                ctx.stroke();
            }

            /* 添加数据DOM */
            function _addRate ({direct='left',data={},point={}}) {
            	let text = document.createElement("div");
                text.className = `rate ${direct}`;
                text.style.cssText = `top:${point.endPoint[1]}px;`;
                text.innerHTML = `${data.cost} <br/> ${data.category}`;

                return text;
            }

        }

        addPie({
            data: [{
                cost: 4.94,
                category: '通讯',
                color: "#e95e45",
            }, {
                cost: 4.78,
                category: '服装美容',
                color: "#20b6ab",
            }, {
                cost: 4.00,
                category: '交通出行',
                color: "#ef7340",
            }, {
                cost: 3.00,
                category: '饮食',
                color: "#eeb328",
            }, {
                cost: 49.40,
                category: '其他',
                color: "#f79954",
            }, {
                cost: 28.77,
                category: '生活日用',
                color: "#00a294",
            }]
        })

	</script>
</body>
</html>